package com.chaoyer.cynet.access.handler.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chaoyer.cynet.common.constant.SysErrors;
import com.chaoyer.cynet.common.context.HandlerContext;
import com.chaoyer.cynet.common.context.CyNetContext;
import com.chaoyer.cynet.common.domain.ErrorInfo;
import com.chaoyer.cynet.common.domain.WebResult;
import com.chaoyer.cynet.common.exception.CyNetException;
import com.chaoyer.cynet.common.exception.HttpRequestMethodNotSupportedException;
import com.chaoyer.cynet.common.exception.NoHandlerFoundException;
import com.chaoyer.cynet.common.exception.ValidateException;
import com.chaoyer.cynet.common.help.SpringHelper;
import com.chaoyer.cynet.common.route.RouterMapping;
import com.chaoyer.cynet.common.utils.CommonUtils;
import com.google.common.base.Charsets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;


import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

@Slf4j
public class HttpServerHandler extends ChannelInboundHandlerAdapter {


    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");
    private static final AsciiString CONNECTION = AsciiString.cached("Connection");
    private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");

    // websocket uri
    private static final String WSURI = "/ws";


    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        // 没读到消息直接关闭连接
        if (msg == null) {
            ctx.close();
        }

        long startTime = System.currentTimeMillis();

        // HTTP request
        if (msg instanceof FullHttpRequest) {

            FullHttpRequest req = (FullHttpRequest) msg;
            // inprove to websocket
            if (WSURI.equalsIgnoreCase(req.uri())) {
                ctx.fireChannelRead(req.retain());
            } else {
                // ignore "/favicon.ico"  "/robots.txt" request
                if (req.uri().equals("/favicon.ico") || req.uri().equals("/robots.txt")) {
                    return;
                }
                // 处理http请求
                deelHttpRequest(ctx, req);
            }

        }

        log.info("req consume time {}", System.currentTimeMillis() - startTime);

    }

    /**
     * http请求统一异常处理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

        WebResult result;
        // 是JDK自带的异常，直接抛出
        String className = cause.getClass().getName();
        if (className.startsWith("java.") || className.startsWith("javax.")) {
            log.error("server is error", cause);
            result = WebResult.error(new ErrorInfo(500L, cause.getMessage(), cause.getMessage()));
        } else if (cause instanceof CyNetException) {
            log.warn("【warn】: {}", cause.getMessage());
            result = WebResult.error(((CyNetException) cause).getErrorInfo());
        } else if (cause instanceof NoHandlerFoundException) {
            log.warn("【not found router】: {}", cause.getMessage());
            result = WebResult.error(SysErrors.NOT_FOUND_ROUTER.setMsg(cause.getMessage()));
        } else if (cause instanceof ValidateException) {
            log.warn("valid error {}", cause.getMessage());
            result = WebResult.error(((ValidateException) cause).getErrorInfo());
        } else {
            // 未知异常
            log.error("【服务器发生错误】: {}", cause.getMessage());
            cause.printStackTrace();
            result = WebResult.error(SysErrors.SERVER_HAS_ERROR);
        }
        sendResp(ctx, result);
    }

    /**
     * handle http request
     *
     * @param req http 请求封装
     */
    private void deelHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {

        // 返回结果
        Object result;

        // url解码
        QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
        // 路由分发handler
        HashMap<String, HandlerContext> methodMap = RouterMapping.get(req.method());
        // 不支持的请求方法
        if (methodMap == null) {
            throw new HttpRequestMethodNotSupportedException(req.method());
        }
        HandlerContext m = methodMap.get(decoder.path());
        // url中?param参数
        Map<String, List<String>> query = queryParams(decoder);
        // 构造请求体body
        Map<String, List<Object>> body = bodyParams(req);

        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
        // 组装请求
        CyNetContext cyNetContext = new CyNetContext(ctx, req, response, query, body);

        if (null == m) {
            throw new NoHandlerFoundException(req.method().name(), req.uri(), req.headers());
        } else {
            try {
                result = m.getMethod().invoke(SpringHelper.getBean(m.getClazz().getClass()), cyNetContext);
                sendResp(ctx, cyNetContext, result);
            } catch (InvocationTargetException e) {
                throw (Exception) e.getTargetException();
            }
        }
    }

    /**
     * 响应客户端
     */
    private void sendResp(ChannelHandlerContext ctx, CyNetContext cyNetContext, Object result) {

        boolean keepAlive = true;

        if (cyNetContext.freq != null) {
            keepAlive = HttpUtil.isKeepAlive(cyNetContext.freq);
        }

        String json = JSONObject.toJSONString(CommonUtils.filterJson(result));
        ByteBuf buf = Unpooled.copiedBuffer(json, CharsetUtil.UTF_8);

        // 响应response
        FullHttpResponse resp = cyNetContext.resp;
        // 写入数据
        resp.content().writeBytes(buf);

        // 返回类型
        if (result instanceof String) {
            resp.headers().set(CONTENT_TYPE, "text/plain");
        } else {
            resp.headers().set(CONTENT_TYPE, "application/json");
        }
        // 设置响应字节大小
        resp.headers().setInt(CONTENT_LENGTH, resp.content().readableBytes());

        log.info("[SEND]" + result);

        if (!keepAlive) {
            ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
        } else {
            resp.headers().set(CONNECTION, KEEP_ALIVE);
            ctx.writeAndFlush(resp);
        }
    }

    private void sendResp(ChannelHandlerContext ctx, Object result) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
        CyNetContext cyNetContext = new CyNetContext();
        cyNetContext.resp = response;
        sendResp(ctx, cyNetContext, result);
    }


    private Map<String, List<String>> queryParams(QueryStringDecoder decoder) {
        return decoder.parameters();
    }

    private Map<String, List<Object>> bodyParams(FullHttpRequest req) throws IOException {

        HashMap<String, List<Object>> body = new HashMap<>();

        if (HttpMethod.POST.equals(req.method())
                || HttpMethod.PUT.equals(req.method())) {
            HttpHeaders headers = req.headers();

            if (headers.get(HttpHeaderNames.CONTENT_TYPE) == null) {
                return body;
            }
            AsciiString contentType = AsciiString.of(headers.get(HttpHeaderNames.CONTENT_TYPE));

            HttpPostRequestDecoder bodyParser = new HttpPostRequestDecoder(req);
            List<InterfaceHttpData> datas = bodyParser.getBodyHttpDatas();
            // 解析"multipart/form-data" "application/x-www-form-urlencoded" 请求
            if (contentType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA)
                    || contentType.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED)) {
                for (InterfaceHttpData data : datas) {
                    if (data.getHttpDataType().equals(InterfaceHttpData.HttpDataType.FileUpload)) {
                        FileUpload fileUpload = (FileUpload) data;
                        body.put(fileUpload.getName(), new ArrayList<Object>() {{
                            add(fileUpload);
                        }});
                    } else {
                        Attribute attribute = (Attribute) data;
                        if (!body.containsKey(data.getName())) {

                            body.put(data.getName(), new ArrayList<Object>() {{
                                add(attribute.getValue());
                            }});
                        } else {
                            body.get(attribute.getName()).add(attribute.getValue());
                        }
                    }
                }
            }

            // 解析 "application/json" 请求
            if (contentType.startsWith(HttpHeaderValues.APPLICATION_JSON)) {
                String jsonStr = req.content().toString(Charsets.UTF_8);
                JSONObject obj = JSON.parseObject(jsonStr);
                for (Map.Entry<String, Object> item : obj.entrySet()) {
                    body.put(item.getKey(), new ArrayList<Object>() {{
                        add(item.getValue());
                    }});
                }
            }
        }
        return body;
    }

}
